栈的定义
栈(stack)是一个特殊的表,它限制了插入和删除只能在一个位置上进行,该位置是表的末端,叫作栈的顶(top)。根据操作的特性,栈也可被称为后进先出(Last In First Out,LIFO)表。对栈的基本操作有push(进栈)
、pop(出栈)
,前者相当于插入,后者则是删除 最后 插入的元素。在一个栈中,唯一可见的元素就是栈顶的元素。
栈的实现
因为栈是一个特殊的表,所以任何实现表的方式都可以用来实现栈。因为栈操作是常数时间的操作,所以除非在非常独特的环境下,栈操作是不可能有明显改进的。在此,我们给出两种常见的实现方式:链式结构和数组。由于实现逻辑比较简单,所以不给出具体代码实现,大家可以自行实现。
栈的链表实现
栈的第一种实现方式是使用单链表。通过在表的顶端插入来实现 push
,通过删除表顶端元素来实现 pop
。top
操作直考察栈顶元素并返回它的值。
其实,修改一下上一讲有关表的链式实现即可完成栈操作:在表末端进行 insert
操作即是实现 push
操作;在表末端进行 remove
操作即是实现 pop
操作;在表末端进行 get
操作即是实现 top
操作。这同时又从反面印证了有关栈的定义。
具体实现大家可以亲自动手试试。
栈的数组实现
用数组来实现栈避免了链,而且可能是更加流行的实现方案。用数组来实现栈,要注意数组的容量、要注意栈顶元素的位置。用数组来实现栈,push
操作和 pop
操作都转化为了简单的赋值操作,而 top
操作则更加简单,就是对于数组元素的取值。
具体实现大家可以亲自动手试试。
栈的应用
平衡符号
写代码时,编译器检查程序的语法错误,例如
(
与)
没有匹配或{
与}
没有匹配等等。对于这种需求,我们可以用栈来实现。假设现在有一组符号
···{ ( } )···
,我们逐一将其push
进栈,当且仅当旧栈顶元素与新栈顶元素匹配时才pop
这两个元素,也就是说当栈内元素为& * ^ % (
时,接下来push())
就会pop())
和pop(()
,即弹出“栈顶”的两个元素。这样一来,如果所有的符号都是匹配的,push
最后一个符号时,栈会进行pop
操作,使得整个栈为空!后缀表达式
假设你现在出去购物,碰巧遇到商场打折:所有的商品均在原价的基础上打88折。在结账清算商品总价时,正确的步骤应该是:计算出商品原总价然后用总价乘0.88即可得到折后总价(或对于每件商品,用其价格乘 0.88之后再相加)。因为几乎所有的计算器都知道乘法的优先级大于加法,所以如果在你输入
10 + 20 + 30 + 40 * 0.88
时,它会输出95.2
而不是我们想要的88
。如果该计算器使用栈来实现的,那就不会出现这样的情况了,具体原因和运算过程大家可以想一想。或者换个情况:只有部分商品才能参与优惠活动。使用科学计算器时我们可以通过加括号的方法得到正确的答案,而一般的简单计算器不包含括号,只支持简单的四则运算!这时候就可以用 栈 + 逆波兰式(reverse Polish)来完成相关需求:在进行运算时若遇到一个运算符号,就在该运算符号所用于栈顶弹出的的两个元素上,并将运算结果压入栈中。例如一个后缀式:
1 2 + 3 *
,运算结果是 9。后缀式可以摈弃一切的优先级。中缀到后缀的转换
栈不仅可以运算后缀式,也可以把一个标准形式的表达式转化为后缀式,即中缀到后缀的转换。此处篇幅较长,就不仔细介绍了,感兴趣的可以好好利用搜索引擎。